namespace StreetCrime
{
    using System;
    using System.Collections.Generic;
    using System.Reactive.Linq;
    using Resources;
    using WP7Contrib.Caching;
    using WP7Contrib.Common.Helpers;
    using WP7Contrib.Communications;
    using WP7Contrib.Logging;
    using WP7Contrib.Services;

    public class StreetLevelCrimeService : ISearchStreetLevelCrime
    {
        private const string FailedPoliceStreetLevelCrime = "StreetLevelCrimeService: Failed to call police service, message - '{0}'";

        private readonly ILog log;
        private readonly IHandleResources resourceClient;
        private readonly ICacheProvider cacheProvider;

        private readonly TimeSpan cacheTimeout;

        public StreetLevelCrimeService(IHandleResources resourceClient,
                                       ICacheProvider cacheProvider,
                                       ILog log,
                                       string url,
                                       string username,
                                       string password)
        {
            this.resourceClient = resourceClient;
            this.cacheProvider = cacheProvider;
            this.log = log;

            this.resourceClient.ForType(ResourceType.Json)
                .UseUrlForGet(url)
                .WithBasicAuthentication(username, password);

            cacheTimeout = new TimeSpan(0, 0, 0, 10);
        }

        public IObservable<StreetLevelCrimeResult> Search(StreetLevelCrimeCriteria criteria)
        {
            try
            {
                var streetLevelCrime = cacheProvider.Get<StreetLevelCrimeCriteria, StreetLevelCrimeResult>(criteria);
                if (streetLevelCrime != null)
                {
                    this.log.Write("StreetLevelCrimeService: Results retrieved from cache, hash code - {0}", criteria.GetHashCode());
                    return Observable.Return(streetLevelCrime).AsObservable();
                }

                return this.resourceClient.Get<List<Result>>(criteria.Latitude, criteria.Longitude)
                                   .Select(response =>
                                        {
                                            var crimes = ProcessResponse(response);
                                            cacheProvider.Add(criteria, crimes, cacheTimeout);
                                            return crimes;
                                        });
            }
            catch (Exception exn)
            {
                var message = string.Format(FailedPoliceStreetLevelCrime, exn.Message);
                this.log.Write(message);
                throw new ServiceException(message, exn);
            }
        }

        private static StreetLevelCrimeResult ProcessResponse(IEnumerable<Result> results)
        {
            var crimesResults = new StreetLevelCrimeResult();
            foreach (var result in results)
            {
                var dateTokens = result.Month.Split('-');

                var crime = new StreetLevelCrime();
                crime.Context = result.Context;
                crime.Category = ParseCategory(result.Category);
                crime.Id = result.Id;

                if (dateTokens.Length == 2)
                {
                    crime.Year = ConversionHelper.ToInt32(dateTokens[0]);
                    crime.Month = ConversionHelper.ToInt32(dateTokens[1]);
                }

                crime.Location = new StreetLevelCrimeLocation();
                crime.Location.Street = new StreetLevelCrimeStreet();

                crime.Location.Latitude = result.Location.Latitude;
                crime.Location.Longitude = result.Location.Longitude;

                crime.Location.Street.Id = result.Location.Street.Id;
                crime.Location.Street.Description = result.Location.Street.Name;
                crime.Location.Street.Name = ExtractName(result.Location.Street.Name);

                crimesResults.AddCrime(crime);
            }

            return crimesResults;
        }

        private static string ExtractName(string description)
        {
            if (string.IsNullOrEmpty(description))
                return description;

            return description.Replace("On or near ", "").Trim();
        }

        private static StreetLevelCrimeCategory ParseCategory(string category)
        {
            if (category == "all-crime")
                return StreetLevelCrimeCategory.All;
            if (category == "burglary")
                return StreetLevelCrimeCategory.Burglary;
            if (category == "anti-social-behaviour")
                return StreetLevelCrimeCategory.AntiSocialBehaviour;
            if (category == "robbery")
                return StreetLevelCrimeCategory.Robbery;
            if (category == "vehicle-crime")
                return StreetLevelCrimeCategory.Vehicle;
            if (category == "violent-crime")
                return StreetLevelCrimeCategory.Violent;

            return StreetLevelCrimeCategory.Other;
        }
    }
}